package com.ftl.search.service.impl;

import com.alibaba.fastjson.JSONObject;
import com.ftl.search.common.enums.ResponseCode;
import com.ftl.search.common.utils.HttpClientUtils;
import com.ftl.search.model.JoinDefinition;
import com.ftl.search.model.QueryDefinition;
import com.ftl.search.model.RestConfigDefinition;
import com.ftl.search.model.RestQueryDefinition;
import com.ftl.search.model.dto.BaseSearchDTO;
import com.ftl.search.model.dto.CUDParamDTO;
import com.ftl.search.model.dto.JoinResultDTO;
import com.ftl.search.model.vo.ResponseVO;
import com.ftl.search.service.BaseSearchBuildService;
import com.ftl.search.service.EsSearchService;
import com.ftl.search.service.SearchConfigService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import org.springframework.util.CollectionUtils;
import org.springframework.util.ObjectUtils;
import org.springframework.util.StringUtils;
import org.springframework.web.bind.annotation.RequestMethod;

import javax.annotation.Resource;
import java.util.*;
import java.util.stream.Collectors;

@Slf4j
@Service
public class CommonSearchService extends EsSearchServiceImpl {

    @Resource
    private BaseSearchBuildService buildService;

    @Resource
    private SearchConfigService searchConfigService;

    @Resource
    private EsSearchService esSearchService;

    public Map<String, List<Map<String, Object>>> multiSearch(String value, List<String> authClients, String template) {
        Map<String, QueryDefinition> queryDefinitionMap = searchConfigService.getSearches();
        Map<String, Set<String>> templates = searchConfigService.getTemplates();
        if (!StringUtils.hasText(template)) {
            template = "DEFAULT";
        }
        Set<String> querySet = templates.get(template);
        List<BaseSearchDTO> searches = new ArrayList<>();
        Map<String, Map<String, String>> prettyFieldMap = new HashMap<>();
        Map<String, List<JoinDefinition>> appendMap = new HashMap<>();
        querySet.forEach(query -> {
            QueryDefinition definition = queryDefinitionMap.get(query);
            searches.add(new BaseSearchDTO(definition.getIndexName(), buildService.buildKeyword(definition, value, null), definition.getKey()));
            if (!CollectionUtils.isEmpty(definition.getPrettyFields())) {
                prettyFieldMap.put(definition.getKey(), definition.getPrettyFields());
            }
            if (!CollectionUtils.isEmpty(definition.getAppend())) {
                appendMap.put(definition.getKey(), definition.getAppend());
            }
        });
        Map<String, List<Map<String, Object>>> result = buildService.queryForEntities(searches, authClients);
        prettyResults(prettyFieldMap, result, appendMap, authClients);
        return result;
    }

    public List<Map<String, Object>> list(String value, String mode, List<String> authClients) {
        Map<String, QueryDefinition> queryDefinitionMap = searchConfigService.getSearches();
        QueryDefinition definition = queryDefinitionMap.get(mode);
        if (Objects.isNull(definition)) {
            return new ArrayList<>();
        }
        List<Map<String, Object>> result = buildService.queryForEntity(definition.getIndexName(), buildService.buildKeyword(definition, value, null), authClients, 3);
        prettyResult(definition.getPrettyFields(), result, definition.getAppend(), authClients);
        return result;
    }

    private void prettyResults(Map<String, Map<String, String>> prettyFieldMap, Map<String, List<Map<String, Object>>> resultMap, Map<String, List<JoinDefinition>> appendMap, List<String> authClients) {
        resultMap.forEach((k, v) -> {
            prettyResult(prettyFieldMap.get(k), v, appendMap.get(k), authClients);
        });
    }

    private void prettyResult(Map<String, String> prettyFields, List<Map<String, Object>> result, List<JoinDefinition> append, List<String> authClients) {
        if (CollectionUtils.isEmpty(prettyFields)) {
            return;
        }
        List<JoinResultDTO> appendResults = new ArrayList<>();
        if (!CollectionUtils.isEmpty(append)) {
            for (JoinDefinition joinDefinition : append) {
                List<String> sourceValues = result.stream().map(item -> (String) item.get(joinDefinition.getSourceKey())).collect(Collectors.toList());
                List<Map<String, Object>> r = batchList(sourceValues, joinDefinition.getTemplate(), authClients, null);
                Map<String, List<Map<String, Object>>> rb = r.stream().collect(Collectors.groupingBy(it -> (String) it.get(joinDefinition.getForeignKey())));
                rb.keySet().forEach(key -> {
                    if ("STRING".equals(joinDefinition.getType()) && StringUtils.hasText(joinDefinition.getTargetKey()) && !CollectionUtils.isEmpty(r)) {
                        appendResults.add(new JoinResultDTO(joinDefinition.getSourceKey(), joinDefinition.getPrettyKey(), key, rb.get(key).get(0).get(joinDefinition.getTargetKey())));
                    } else if ("MAP".equals(joinDefinition.getType()) && !CollectionUtils.isEmpty(r)) {
                        appendResults.add(new JoinResultDTO(joinDefinition.getSourceKey(), joinDefinition.getPrettyKey(), key, rb.get(key).get(0)));
                    } else {
                        appendResults.add(new JoinResultDTO(joinDefinition.getSourceKey(), joinDefinition.getPrettyKey(), key, rb.get(key).subList(0, joinDefinition.getSize())));
                    }
                });
            }
        }
        result.forEach(r -> {
            appendResults.forEach(appendR -> {
                if (appendR.getSourceValue().equals(r.get(appendR.getSourceKey()))) {
                    r.put(appendR.getPrettyKey(), appendR.getValues());
                }
            });
            prettyFields.forEach((k, v) -> {
                if (r.containsKey(k)) {
                    r.put(v, r.get(k));
                }
            });
        });
    }

    /**
     * 批量查询
     * @param ids
     * @param mode
     * @param authClients
     * @return
     */
    public List<Map<String, Object>> batchList(List<String> ids, String mode, List<String> authClients, Integer size) {
        log.info("批量查询入参：{}，mode: {}", String.join(",", ids), mode);
        Map<String, QueryDefinition> queryDefinitionMap = searchConfigService.getSearches();
        QueryDefinition definition = queryDefinitionMap.get(mode);
        if (Objects.isNull(definition)) {
            return new ArrayList<>();
        }
        if (size == null) {
            size = -1;
        }
        List<Map<String, Object>> result = buildService.queryForEntity(definition.getIndexName(), buildService.buildKeyword(definition, null, ids), authClients, size);
        prettyResult(definition.getPrettyFields(), result, definition.getAppend(), authClients);
        return result;
    }

    /**
     * 根据rest请求获取查询结果
     * @param ids
     * @param mode
     * @param options
     * @return
     */
    public List<?> listByRest(List<String> ids, String mode, Map<String, Object> options) {
        Map<String, RestQueryDefinition> restQueryDefinitionMap = searchConfigService.getRestTemplates();
        if (!restQueryDefinitionMap.containsKey(mode)) {
            return new ArrayList<>();
        }
        RestQueryDefinition restQueryDefinition = restQueryDefinitionMap.get(mode);
        Map<String, RestConfigDefinition> restConfigDefinitionMap = searchConfigService.getRestMapping();
        RestConfigDefinition restConfigDefinition = restConfigDefinitionMap.get(restQueryDefinition.getRestName());
        String url = restConfigDefinition.getUrl();
        if (StringUtils.hasText(restConfigDefinition.getPathField())) {
            String key = restConfigDefinition.getPathField();
            if (options.containsKey(key)) {
                url = url.replace("{" + key + "}", options.get(key).toString());
            }
        }
        Map<String, List<String>> paramMap = new HashMap<>();
        if (StringUtils.hasText(restConfigDefinition.getPrimaryKey())) {
            paramMap.put(restConfigDefinition.getPrimaryKey(), ids);
        }
        if (!CollectionUtils.isEmpty(restConfigDefinition.getParamKey())) {
            restConfigDefinition.getParamKey().forEach(paramKey -> {
                if (options.containsKey(paramKey)) {
                    paramMap.put(paramKey, Arrays.asList(options.get(paramKey).toString()));
                }
            });
        }
        Object result = null;
        if (RequestMethod.GET.name().equals(restConfigDefinition.getMethod().toString())) {
            List<Map<String, String>> paramList = new ArrayList<>();
            paramMap.forEach((k, v) -> {
                if (!CollectionUtils.isEmpty(v)) {
                    v.forEach(d -> {
                        Map<String, String> subParam = new HashMap<>();
                        subParam.put(k, d);
                        paramList.add(subParam);
                    });
                }
            });
            result = HttpClientUtils.getParamMap(url, paramList, restConfigDefinition.getHeaders());
        } else {
            result = HttpClientUtils.postMap(url, JSONObject.toJSONString(paramMap), restConfigDefinition.getHeaders());
        }
        if (Objects.isNull(result)) {
            return new ArrayList<>();
        }
        log.info(JSONObject.toJSONString(result));
        if ("MAP".equals(restConfigDefinition.getResultType())) {
            return prettyRestResultFromMap((Map) result, restQueryDefinition.getPrettyFields(), restConfigDefinition.getSortField(), ids);
        }
        if ("LIST".equals(restConfigDefinition.getResultType())) {
            return prettyRestResultFromList((List) result, restQueryDefinition.getPrettyFields(), restConfigDefinition.getSortField());
        }
        return new ArrayList<>();
    }

    /**
     * 格式化查询结果
     * @param result
     * @param prettyFields
     * @param sortField
     * @param ids
     * @return
     */
    private List<Map<String, Object>> prettyRestResultFromMap(Map<String, List<Map<String, Object>>> result, Map<String, String> prettyFields, String sortField, List<String> ids) {
        List<Map<String, Object>> prettyResult = new ArrayList<>();
        ids.forEach(id -> {
            if (!result.containsKey(id)) {
                return;
            }
            List<Map<String, Object>> r = result.get(id);
            if (StringUtils.hasText(sortField)) {
                r.sort(new Comparator<Map<String, Object>>() {
                    @Override
                    public int compare(Map<String, Object> o1, Map<String, Object> o2) {
                        return ((String) o2.get(sortField)).compareTo((String) o1.get(sortField));
                    }
                });
            }
            r.forEach(item -> {
                prettyFields.forEach((k, v) -> {
                    if (item.containsKey(k)) {
                        item.put(v, item.get(k));
                    }
                });
            });
            Map<String, Object> newR = new HashMap<>();
            Map<String, Object> lastR = r.get(0);
            if (CollectionUtils.isEmpty(prettyFields)) {
                throw new IllegalArgumentException("必须配置显示字段: prettyFields");
            }
            prettyFields.forEach((k, v) -> {
                if ("ID".equals(k)) {
                    newR.put(v, id);
                } else if ("TYPE".equals(k)) {
                    newR.put("type", v);
                } else if ("APPEND".equals(k)) {
                    newR.put(v, r);
                } else {
                    newR.put(v, lastR.get(k));
                }
            });
            prettyResult.add(newR);
        });
        return prettyResult;
    }

    /**
     * 格式化查询结果
     * @param result
     * @param prettyFields
     * @param sortField
     * @return
     */
    private List<Map<String, Object>> prettyRestResultFromList(List<Map<String, Object>> result, Map<String, String> prettyFields, String sortField) {
        List<Map<String, Object>> prettyResult = new ArrayList<>();
        result.forEach(r -> {
            Map lastR = new HashMap<>();
            if (StringUtils.hasText(sortField)) {
                List<Map<String, Object>> subR = new ArrayList<>();
                String[] fields = sortField.split("\\.");
                // todo 目前只支持二级检索
                if (fields.length >= 2) {
                    subR = (List) r.get(fields[0]);
                    subR.sort(new Comparator<Map<String, Object>>() {
                        @Override
                        public int compare(Map<String, Object> o1, Map<String, Object> o2) {
                            return ((String) o2.get(fields[1])).compareTo((String) o1.get(fields[1]));
                        }
                    });
                }
                r.put(fields[0], subR.stream().map(s -> {
                    Map<String, Object> n = new HashMap<>();
                    prettyFields.forEach((k, v) -> {
                        if (s.containsKey(k)) {
                            n.put(v, s.get(k));
                        }
                    });
                    return n;
                }).collect(Collectors.toList()));
                lastR = (Map) ((List) r.get(fields[0])).stream().findFirst().orElse(new HashMap<>());
            }
            Map<String, Object> newR = new HashMap<>();
            if (CollectionUtils.isEmpty(prettyFields)) {
                throw new IllegalArgumentException("必须配置显示字段: prettyFields");
            }
            Map finalLastR = lastR;
            prettyFields.forEach((k, v) -> {
                if ("TYPE".equals(k)) {
                    newR.put("type", v);
                }
                if (r.containsKey(k)){
                    newR.put(v, r.get(k));
                } else if (CollectionUtils.isEmpty(finalLastR) && finalLastR.containsKey(k)) {
                    newR.put(v, finalLastR.get(k));
                }
            });
            prettyResult.add(newR);
        });

        return prettyResult;
    }

    /**
     * 只对data和id进行特殊校验，index的校验交个oval
     * @param dto
     * @return
     */
    private ResponseVO validate(CUDParamDTO dto) {
        switch (dto.getOperate()) {
            case UPDATE:
            case PARTIAL_UPDATE:
            case CREATE_DATA:
                if (Objects.isNull(dto.getData())) {
                    return new ResponseVO(ResponseCode.ERROR.name(), "插入或更新数据操作时 data 不能为空！", dto);
                }
                break;
            case DELETE_DATA:
                if (Objects.isNull(dto.getData()) && !StringUtils.hasText(dto.getId())) {
                    return new ResponseVO(ResponseCode.ERROR.name(), "删除数据操作时 data 和 id 不能全为空", dto);
                }
                break;
            default:
                break;
        }
        return null;
    }

    /**
     * 对ES服务进行操作
     * @param dto CRUDParamDTO
     * @return ResponseVO
     */
    public ResponseVO modifyWithES(CUDParamDTO dto) {
        ResponseVO responseVO = validate(dto);
        if (responseVO != null) {
            return responseVO;
        }
        try {
            switch (dto.getOperate()) {
                case CREATE_INDEX:
                    esSearchService.createIndex(dto.getIndexName());
                    return new ResponseVO(ResponseCode.SUCCESS.name(), String.format("索引 %s 创建成功!", dto.getIndexName()), dto);
                case DELETE_INDEX:
                    esSearchService.deleteIndex(dto.getIndexName());
                    return new ResponseVO(ResponseCode.SUCCESS.name(), String.format("索引 %s 删除成功!", dto.getIndexName()), dto);
                case CREATE_DATA:
                    esSearchService.save(dto.getIndexName(), dto.getData());
                    return new ResponseVO(ResponseCode.SUCCESS.name(), "数据保存成功", dto);
                case UPDATE:
                    if (StringUtils.hasText(dto.getId())) {
                        esSearchService.updateByDocumentId(dto.getIndexName(), dto.getId(), dto.getData());
                    } else {
                        esSearchService.save(dto.getIndexName(), dto.getData());
                    }
                    return new ResponseVO(ResponseCode.SUCCESS.name(), "数据更新成功", dto);
                case PARTIAL_UPDATE:
                    esSearchService.updatePartialFields(dto.getIndexName(), dto.getId(), dto.getData());
                    return new ResponseVO(ResponseCode.SUCCESS.name(), "数据更新成功", dto);
                case DELETE_DATA:
                    if (StringUtils.hasText(dto.getId())) {
                        esSearchService.deleteByDocumentId(dto.getIndexName(), dto.getId());
                    } else {
                        esSearchService.deleteByObject(dto.getIndexName(), dto.getData());
                    }
                default:
                    return new ResponseVO(ResponseCode.NOT_EXISTS.name(), "Operate 参数输入有误", dto);
            }
        } catch (Exception e) {
            e.printStackTrace();
            return new ResponseVO(ResponseCode.ERROR.name(), String.format("操作出现异常，内容为：%s", e.getMessage()), dto);
        }
    }

    public boolean checkRecord(CUDParamDTO dto) {
        if (dto == null) {
            return false;
        }
        if (!StringUtils.hasText(dto.getIndexName())) {
            return false;
        }
        if (ObjectUtils.isEmpty(dto.getOperate())) {
            return false;
        }
        ResponseVO responseVO = validate(dto);
        return responseVO == null;
    }
}
